import numpy as np
import pandas as pd
from scipy.optimize import minimize
import plotly.graph_objects as go
import matplotlib.pyplot as plt
swap_curve = pd.read_excel("market_data_2023 (1).xlsx", sheet_name=1)
swap_curve = swap_curve.iloc[2:].reset_index(drop=True)
swap_curve.columns = ['Maturity', 'Swap rates (in %)']
swap_curve = swap_curve.dropna()
maturities_ex1 = swap_curve['Maturity'].values
swap_rates = swap_curve['Swap rates (in %)'].values
def convert_maturity(maturity):
"""Converte una scadenza in anni."""
if 'M' in maturity:
return int(maturity.replace('M', '')) / 12
elif 'Y' in maturity:
return int(maturity.replace('Y', ''))
else:
raise ValueError(f"Formato di maturità non riconosciuto: {maturity}")
maturities_ex1 = [convert_maturity(m) for m in maturities_ex1]
swap_rates = swap_rates / 100
df = pd.DataFrame({'Maturity': maturities_ex1, 'swap_rates': swap_rates})
df.head(5)
| Maturity | swap_rates | |
|---|---|---|
| 0 | 0.083333 | 0.02516 |
| 1 | 0.250000 | 0.02647 |
| 2 | 0.500000 | 0.0308 |
| 3 | 0.750000 | 0.0314 |
| 4 | 1.000000 | 0.03239 |
maturity = df['Maturity']
rate = df['swap_rates']
def NelsonSiegelSvensson(time_to_maturity, b0, b1, b2, b3, t1, t2):
predicted_yield = (
b0 +
b1 * (1 - np.exp(-time_to_maturity / t1)) / (time_to_maturity / t1) +
b2 * ((1 - np.exp(-time_to_maturity / t1)) / (time_to_maturity / t1) - np.exp(-time_to_maturity / t1)) +
b3 * ((1 - np.exp(-time_to_maturity / t2)) / (time_to_maturity / t2) - np.exp(-time_to_maturity / t2))
)
return predicted_yield
# Obejective Function
def NSSMinimize(params, time_to_maturity, observed_yields):
def NSSGoodFit(params, time_to_maturity, observed_yields):
b0, b1, b2, b3, t1, t2 = params
predicted_yields = NelsonSiegelSvensson(time_to_maturity, b0, b1, b2, b3, t1, t2)
residuals = predicted_yields - observed_yields
return np.sum(residuals ** 2)
constraints = [
{'type': 'ineq', 'fun': lambda x: x[0]}, # b0 > 0
{'type': 'ineq', 'fun': lambda x: x[1] + x[2]}, # b1 + b2 > 0
{'type': 'ineq', 'fun': lambda x: x[4]}, # t1 > 0
{'type': 'ineq', 'fun': lambda x: x[5]} # t2 > 0
]
opt_solution = minimize(
NSSGoodFit,
params,
args=(time_to_maturity, observed_yields),
method="SLSQP",
)
return opt_solution.x
initial_params_1 = (0.03, -0.02, 0.01, 0.005, 1.0, 2.0)
initial_params_2 = (0.025, -0.015, 0.008, 0.004, 1.2, 2.5)
initial_params_3 = (0.035, -0.03, 0.015, 0.006, 0.9, 2.1)
initial_params_4 = (0.1, 1.5, 1, 0.4, 0.9, 2.)
# Compute
optimal_1 = NSSMinimize(initial_params_1, maturity, rate)
optimal_2 = NSSMinimize(initial_params_2, maturity, rate)
optimal_3 = NSSMinimize(initial_params_3, maturity, rate)
optimal_4 = NSSMinimize(initial_params_4, maturity, rate)
mat = np.linspace(1, 30, 30)
yields_1 = NelsonSiegelSvensson(mat, *optimal_1)
yields_2 = NelsonSiegelSvensson(mat, *optimal_2)
yields_3 = NelsonSiegelSvensson(mat, *optimal_3)
yields_4 = NelsonSiegelSvensson(mat, *optimal_4)
# Create DataFrames for each set of yields
new_df = pd.DataFrame({'NSSrates1': yields_1}, index=mat)
new_df['NSSrates2'] = yields_2
new_df['NSSrates3'] = yields_3
new_df['NSSrates4'] = yields_4
new_df.head(5)
| NSSrates1 | NSSrates2 | NSSrates3 | NSSrates4 | |
|---|---|---|---|---|
| 1.0 | 0.031240 | 0.030604 | 0.031250 | 0.031440 |
| 2.0 | 0.029729 | 0.029721 | 0.029496 | 0.029051 |
| 3.0 | 0.027030 | 0.027506 | 0.026765 | 0.026349 |
| 4.0 | 0.024972 | 0.025514 | 0.024789 | 0.024672 |
| 5.0 | 0.023743 | 0.024111 | 0.023655 | 0.023800 |
fig_nss = go.Figure()
# LINE
fig_nss.add_trace(go.Scatter(
x=new_df.index,
y=new_df["NSSrates1"],
mode='lines',
name="Model NSS1",
line=dict(width=1.3, dash = 'dash')
))
# LINE
fig_nss.add_trace(go.Scatter(
x=new_df.index,
y=new_df["NSSrates2"],
mode='lines',
name="Model NSS2",
line=dict(width=1.3, dash = 'dash')
))
# LINE
fig_nss.add_trace(go.Scatter(
x=new_df.index,
y=new_df["NSSrates3"],
mode='lines',
name="Model NSS3",
line=dict(width=1.3, dash = 'dash')
))
# LINE
fig_nss.add_trace(go.Scatter(
x=new_df.index,
y=new_df["NSSrates4"],
mode='lines',
name="Model NSS4",
line=dict(width=1.3, dash = 'dash')
))
#SCATTER
fig_nss.add_trace(go.Scatter(
x=df.Maturity,
y=df["swap_rates"],
mode='markers',
name="Swap Rates",
marker=dict(size=6 ,color = 'orangered')
))
fig_nss.update_layout(
title="Model NSS vs Swap Rates",
xaxis_title="Maturity",
yaxis_title="Value",
font=dict(family="Times New Roman", size=10, color="Black"),
template="plotly_white",
showlegend=True,
width=1000,
height=500
)
# Show the figure
fig_nss.show()
Maturity = np.linspace(-3,30,34)
df3 = pd.DataFrame({'Maturity':Maturity})
df3.loc[0] = [0.083333]
df3.loc[1] = [0.25]
df3.loc[2] = [0.5]
df3.loc[3] = [0.75]
swap_rates = [0.02516, 0.02647, 0.0308, 0.0314]
df3["swap_rates"] = np.nan
df3.loc[:3, "swap_rates"] = swap_rates
df3.loc[4:, "swap_rates"] = new_df["NSSrates1"].values
df3
| Maturity | swap_rates | |
|---|---|---|
| 0 | 0.083333 | 0.025160 |
| 1 | 0.250000 | 0.026470 |
| 2 | 0.500000 | 0.030800 |
| 3 | 0.750000 | 0.031400 |
| 4 | 1.000000 | 0.031240 |
| 5 | 2.000000 | 0.029729 |
| 6 | 3.000000 | 0.027030 |
| 7 | 4.000000 | 0.024972 |
| 8 | 5.000000 | 0.023743 |
| 9 | 6.000000 | 0.023137 |
| 10 | 7.000000 | 0.022928 |
| 11 | 8.000000 | 0.022947 |
| 12 | 9.000000 | 0.023087 |
| 13 | 10.000000 | 0.023282 |
| 14 | 11.000000 | 0.023496 |
| 15 | 12.000000 | 0.023708 |
| 16 | 13.000000 | 0.023909 |
| 17 | 14.000000 | 0.024096 |
| 18 | 15.000000 | 0.024265 |
| 19 | 16.000000 | 0.024419 |
| 20 | 17.000000 | 0.024557 |
| 21 | 18.000000 | 0.024682 |
| 22 | 19.000000 | 0.024796 |
| 23 | 20.000000 | 0.024898 |
| 24 | 21.000000 | 0.024991 |
| 25 | 22.000000 | 0.025076 |
| 26 | 23.000000 | 0.025154 |
| 27 | 24.000000 | 0.025226 |
| 28 | 25.000000 | 0.025292 |
| 29 | 26.000000 | 0.025352 |
| 30 | 27.000000 | 0.025409 |
| 31 | 28.000000 | 0.025461 |
| 32 | 29.000000 | 0.025510 |
| 33 | 30.000000 | 0.025555 |
df3["ZCB_price"] = np.nan
df3["Zero_Rate"] = np.nan
notional = 1.0
for i, row in df3.iterrows():
if row["Maturity"] <= 1.0:
rate = row["swap_rates"]
df3.loc[i, "Zero_Rate"] = rate
df3.loc[i, "ZCB_price"] = np.exp(-rate * row["Maturity"])
for i, row in df3.iterrows():
if row["Maturity"] > 1.0:
previous_cashflows = [
(df3.iloc[j]["swap_rates"] * notional / 2 ) * df3.iloc[j]["ZCB_price"]
for j in range(int(i))
]
total_cashflows = sum(previous_cashflows)
maturity = row["Maturity"]
coupon = row["swap_rates"] * notional / 2
principal_discount = notional
zcb_price = (notional - total_cashflows) / (coupon + principal_discount)
zero_rate = -np.log(zcb_price) / maturity
df3.loc[i, "ZCB_price"] = zcb_price
df3.loc[i, "Zero_Rate"] = zero_rate
df3
| Maturity | swap_rates | ZCB_price | Zero_Rate | |
|---|---|---|---|---|
| 0 | 0.083333 | 0.025160 | 0.997906 | 0.025160 |
| 1 | 0.250000 | 0.026470 | 0.993404 | 0.026470 |
| 2 | 0.500000 | 0.030800 | 0.984718 | 0.030800 |
| 3 | 0.750000 | 0.031400 | 0.976725 | 0.031400 |
| 4 | 1.000000 | 0.031240 | 0.969243 | 0.031240 |
| 5 | 2.000000 | 0.029729 | 0.915058 | 0.044384 |
| 6 | 3.000000 | 0.027030 | 0.902856 | 0.034064 |
| 7 | 4.000000 | 0.024972 | 0.891722 | 0.028650 |
| 8 | 5.000000 | 0.023743 | 0.881260 | 0.025281 |
| 9 | 6.000000 | 0.023137 | 0.871182 | 0.022984 |
| 10 | 7.000000 | 0.022928 | 0.861308 | 0.021329 |
| 11 | 8.000000 | 0.022947 | 0.851538 | 0.020089 |
| 12 | 9.000000 | 0.023087 | 0.841820 | 0.019132 |
| 13 | 10.000000 | 0.023282 | 0.832133 | 0.018376 |
| 14 | 11.000000 | 0.023496 | 0.822471 | 0.017767 |
| 15 | 12.000000 | 0.023708 | 0.812836 | 0.017269 |
| 16 | 13.000000 | 0.023909 | 0.803233 | 0.016855 |
| 17 | 14.000000 | 0.024096 | 0.793671 | 0.016506 |
| 18 | 15.000000 | 0.024265 | 0.784157 | 0.016210 |
| 19 | 16.000000 | 0.024419 | 0.774699 | 0.015955 |
| 20 | 17.000000 | 0.024557 | 0.765302 | 0.015734 |
| 21 | 18.000000 | 0.024682 | 0.755972 | 0.015542 |
| 22 | 19.000000 | 0.024796 | 0.746715 | 0.015372 |
| 23 | 20.000000 | 0.024898 | 0.737533 | 0.015222 |
| 24 | 21.000000 | 0.024991 | 0.728431 | 0.015089 |
| 25 | 22.000000 | 0.025076 | 0.719411 | 0.014969 |
| 26 | 23.000000 | 0.025154 | 0.710475 | 0.014862 |
| 27 | 24.000000 | 0.025226 | 0.701626 | 0.014765 |
| 28 | 25.000000 | 0.025292 | 0.692864 | 0.014677 |
| 29 | 26.000000 | 0.025352 | 0.684191 | 0.014597 |
| 30 | 27.000000 | 0.025409 | 0.675608 | 0.014524 |
| 31 | 28.000000 | 0.025461 | 0.667115 | 0.014457 |
| 32 | 29.000000 | 0.025510 | 0.658713 | 0.014395 |
| 33 | 30.000000 | 0.025555 | 0.650403 | 0.014339 |
save = df3.to_csv('ZCB_prices.csv')